﻿using System;
using System.Collections.Generic;
using MugenInjection.Bindings;
using MugenInjection.Delegates;
using MugenInjection.Exceptions;
using MugenInjection.Infrastructure;
using MugenInjection.Interface;
using MugenInjection.Interface.Components;

// ReSharper disable CheckNamespace
namespace MugenInjection.Core.Components
// ReSharper restore CheckNamespace
{
    /// <summary>
    /// Represents the base class for components that manages the <see cref="IBinding"/>s.
    /// </summary>
    public class BindingManagerComponent : Component, IBindingManagerComponent
    {
        #region Fields

        private readonly CacheCollection<Type, IBinding> _selfBindableCache = new CacheCollection<Type, IBinding>();
        private readonly Dictionary<int, IBinding> _cachedBindings = new Dictionary<int, IBinding>();
        private bool _isCacheBinding;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="BindingManagerComponent"/> class.
        /// </summary>
        public BindingManagerComponent()
        {
            IsCacheBinding = true;
        }

        #endregion

        #region Implementation of IBindingManagerComponent

        /// <summary>
        /// Gets or sets the value, if <c>true</c> cache bindings, otherwise <c>false</c>.
        /// </summary>
        public bool IsCacheBinding
        {
            get { return _isCacheBinding; }
            set
            {
                if (_isCacheBinding == value) return;
                _isCacheBinding = value;
                _cachedBindings.Clear();
            }
        }

        /// <summary>
        /// Searches for a <see cref="IBinding"/> satisfying the specified a <see cref="IBindingContext"/>
        /// </summary>
        /// <param name="bindingContext">The specified <see cref="IBindingContext"/> for search.</param>
        /// <returns>Found a <see cref="IBinding"/>.</returns>
        public virtual IBinding FindBinding(IBindingContext bindingContext)
        {
            if (!_isCacheBinding)
                return GetPriorityBinding(bindingContext, Injector.Bindings.WhereCanResolve(bindingContext));
            int value = bindingContext.GetSnapshot();
            lock (_cachedBindings)
            {
                IBinding binding;
                if (!_cachedBindings.TryGetValue(value, out binding))
                {
                    binding = GetPriorityBinding(bindingContext, Injector.Bindings.WhereCanResolve(bindingContext));
                    if (binding != null && binding.Settings.IsContain(SpecialParameterKeys.NoCacheBindingKey))
                        return binding;
                    _cachedBindings[value] = binding;
                }
                return binding;
            }
        }

        /// <summary>
        /// Searches all <see cref="IBinding"/>s satisfying the specified a <see cref="Type"/>
        /// </summary>
        /// <param name="service">The specified <see cref="Type"/> for search.</param>
        /// <returns>Found a <see cref="IBinding"/>s.</returns>
        public virtual IList<IBinding> FindAllBinding(Type service)
        {
            return Injector.Bindings.WhereServiceEqual(service);
        }

        /// <summary>
        /// Searches all <see cref="IBinding"/>s satisfying the specified <see cref="IBindingContext"/>
        /// </summary>
        /// <param name="bindingContext">The specified <see cref="IBindingContext"/> for search.</param>
        /// <returns>Found a <see cref="IBinding"/>s.</returns>
        public virtual IEnumerable<IBinding> FindAllBinding(IBindingContext bindingContext)
        {
            return Injector.Bindings.WhereCanResolve(bindingContext);
        }

        /// <summary>
        /// Selects binding with high priority (check duplicate bindings).
        /// </summary>
        /// <param name="bindingContext">The specified <see cref="IBindingContext"/>.</param>
        /// <param name="bindings">Specifid <see cref="IEnumerator{IBinding}"/> for select <see cref="IBinding"/>.</param>
        /// <returns>Selected a <see cref="IBinding"/>.</returns>
        public virtual IBinding GetPriorityBinding(IBindingContext bindingContext, IEnumerable<IBinding> bindings)
        {
            IBinding hightPriority = null;
            foreach (IBinding currentItem in bindings)
            {
                if (hightPriority == null || currentItem.Priority > hightPriority.Priority)
                {
                    hightPriority = currentItem;
                    continue;
                }

                //Checking priority
                if (currentItem.Priority < hightPriority.Priority) continue;

                //Cheking duplicate bindings.
                if (currentItem.Priority == hightPriority.Priority)
                    throw new ConflictBindingException(bindingContext);
            }
            return hightPriority;
        }

        /// <summary>
        /// Creates a self bindable <see cref="IBinding"/>.
        /// </summary>
        /// <param name="service">The specified <see cref="Type"/> for self bindable.</param>
        /// <param name="activator">The specified factory to create <see cref="IActivator"/>. </param>
        /// <param name="constructorResolver">The specified factory to create <see cref="IConstructorResolver"/>. </param>
        /// <param name="useCache"><c>True</c> - use cache for create a <see cref="IBinding"/>; otherwise <c>false</c>.</param>
        /// <returns>An instance of <see cref="IBinding"/>.</returns>
        public virtual IBinding CreateSelfBindable(Type service, Func<IActivator> activator,
                                                   Func<IConstructorResolver> constructorResolver, bool useCache)
        {
            Validate.ArgumentNotNull(activator, "activator");
            Validate.ArgumentNotNull(constructorResolver, "constructorResolver");
            if (!InjectorUtilsInternal.IsSelfBindable(service))
                return null;
            if (!useCache)
                return TypeBinding.CreateSelfBinding(service, activator(), constructorResolver());
            lock (_selfBindableCache)
            {
                var binding = _selfBindableCache[service];
                if (binding == null)
                {
                    binding = TypeBinding.CreateSelfBinding(service, activator(), constructorResolver());
                    _selfBindableCache[service] = binding;
                }
                return binding;
            }
        }

        #endregion

        #region Methods

        private void BindingsOnAddedBindingBuilder(IBindingBuilder bindingBuilder)
        {
            if (!IsCacheBinding) return;
            lock (_cachedBindings)
                _cachedBindings.Clear();
        }

        private void BindingsOnAddedBinding(IBinding binding)
        {
            if (!IsCacheBinding) return;
            lock (_cachedBindings)
                _cachedBindings.Clear();
            if (!binding.Settings.IsContain(SpecialParameterKeys.NoCacheBindingKey)) return;
            foreach (var service in binding.Services)
            {
                foreach (var b in FindAllBinding(service))
                    b.Settings[SpecialParameterKeys.NoCacheBindingKey] = true;
            }
        }

        #endregion

        #region Overrides of Component

        /// <summary>
        /// Indicates whether the component is unique.
        /// </summary>
        public override sealed bool IsSingle
        {
            get { return true; }
        }

        /// <summary>
        /// Returns the component type. Need to find a component in the container.
        /// </summary>
        public override sealed Type ComponentType
        {
            get { return typeof(IBindingManagerComponent); }
        }

        /// <summary>
        /// Creates a new object that is a copy of the current instance.
        /// </summary>
        /// <returns>
        /// A new object that is a copy of this instance.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        public override IComponent Clone()
        {
            return new BindingManagerComponent { IsCacheBinding = IsCacheBinding };
        }

        /// <summary>
        /// Occurs on load component.
        /// </summary>
        protected override void OnLoad()
        {
            Injector.Bindings.AddedBinding += BindingsOnAddedBinding;
            Injector.Bindings.RemovedBinding += BindingsOnAddedBinding;
            Injector.Bindings.AddedBindingBuilder += BindingsOnAddedBindingBuilder;
            Injector.Bindings.RemovedBindingBuilder += BindingsOnAddedBindingBuilder;
            base.OnLoad();
        }

        /// <summary>
        /// Occurs on unload component.
        /// </summary>
        protected override void OnUnload()
        {
            Injector.Bindings.AddedBinding -= BindingsOnAddedBinding;
            Injector.Bindings.RemovedBinding -= BindingsOnAddedBinding;
            Injector.Bindings.AddedBindingBuilder -= BindingsOnAddedBindingBuilder;
            Injector.Bindings.RemovedBindingBuilder -= BindingsOnAddedBindingBuilder;

            lock (_cachedBindings)
                _cachedBindings.Clear();
            lock (_selfBindableCache)
            {
                foreach (IBinding binding in _selfBindableCache.Cache.Values)
                    binding.Dispose();
                _selfBindableCache.Clear();
            }
        }

        #endregion
    }
}